const gf = (init:any) => {
    var i, r = new Float64Array(16);
    if (init) for (i = 0; i < init.length; i++) r[i] = init[i];
    return r;
};

const gf0 = gf(false),
    gf1 = gf([1]),
    D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]),
    I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]);

const  car25519 = (o:any) => {
    var c;
    var i;
    for (i = 0; i < 16; i++) {
        o[i] += 65536;
        c = Math.floor(o[i] / 65536);
        o[(i+1)*(i<15?1:0)] += c - 1 + 37 * (c-1) * (i===15?1:0);
        o[i] -= (c * 65536);
    }
}

const sel25519 = (p:any, q:any, b:any)=> {
    var t, c = ~(b-1);
    for (var i = 0; i < 16; i++) {
        t = c & (p[i] ^ q[i]);
        p[i] ^= t;
        q[i] ^= t;
    }
}

const unpack25519 = (o:any, n:any)=> {
    var i;
    for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8);
    o[15] &= 0x7fff;
}

// addition
const A = (o:any, a:any, b:any) => {
    var i;
    for (i = 0; i < 16; i++) o[i] = (a[i] + b[i])|0;
}

// subtraction
const Z = (o:any, a:any, b:any) => {
    var i;
    for (i = 0; i < 16; i++) o[i] = (a[i] - b[i])|0;
}

// multiplication
const M = (o:any, a:any, b:any) => {
    var i, j, t = new Float64Array(31);
    for (i = 0; i < 31; i++) t[i] = 0;
    for (i = 0; i < 16; i++) {
        for (j = 0; j < 16; j++) {
            t[i+j] += a[i] * b[j];
        }
    }
    for (i = 0; i < 15; i++) {
        t[i] += 38 * t[i+16];
    }
    for (i = 0; i < 16; i++) o[i] = t[i];
    car25519(o);
    car25519(o);
}

// squaring
const S = (o:any, a:any) => {
    M(o, a, a);
}

// inversion
const inv25519 = (o:any, i:any) => {
    var c = gf(false);
    var a;
    for (a = 0; a < 16; a++) c[a] = i[a];
    for (a = 253; a >= 0; a--) {
        S(c, c);
        if(a !== 2 && a !== 4) M(c, c, i);
    }
    for (a = 0; a < 16; a++) o[a] = c[a];
}

const pack25519 = (o:any, n:any) => {
    var i, j, b;
    var m = gf(false), t = gf(false);
    for (i = 0; i < 16; i++) t[i] = n[i];
    car25519(t);
    car25519(t);
    car25519(t);
    for (j = 0; j < 2; j++) {
        m[0] = t[0] - 0xffed;
        for (i = 1; i < 15; i++) {
            m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1);
            m[i-1] &= 0xffff;
        }
        m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1);
        b = (m[15]>>16) & 1;
        m[14] &= 0xffff;
        sel25519(t, m, 1-b);
    }
    for (i = 0; i < 16; i++) {
        o[2*i] = t[i] & 0xff;
        o[2*i+1] = t[i] >> 8;
    }
}

const par25519 = (a:any) =>{
    var d = new Uint8Array(32);
    pack25519(d, a);
    return d[0] & 1;
}

const vn = (x:any, xi:any, y:any, yi:any, n:any) => {
    var i, d = 0;
    for (i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i];
    return (1 & ((d - 1) >>> 8)) - 1;
}

const crypto_verify_32 = (x:any, xi:any, y:any, yi:any) =>{
    return vn(x, xi, y, yi, 32);
}

const neq25519 = (a:any, b:any) => {
    var c = new Uint8Array(32), d = new Uint8Array(32);
    pack25519(c, a);
    pack25519(d, b);
    return crypto_verify_32(c, 0, d, 0);
}

const pow2523 = (o:any, i:any) =>{
    var c = gf(false);
    var a;
    for (a = 0; a < 16; a++) c[a] = i[a];
    for (a = 250; a >= 0; a--) {
        S(c, c);
        if (a !== 1) M(c, c, i);
    }
    for (a = 0; a < 16; a++) o[a] = c[a];
}

const set25519 = (r:any, a:any) => {
    var i;
    for (i = 0; i < 16; i++) r[i] = a[i] | 0;
}

const unpackneg = (r:any, p:any) => {
    var t = gf(false), chk = gf(false), num = gf(false),
        den = gf(false), den2 = gf(false), den4 = gf(false),
        den6 = gf(false);

    set25519(r[2], gf1);
    unpack25519(r[1], p);
    S(num, r[1]);
    M(den, num, D);
    Z(num, num, r[2]);
    A(den, r[2], den);

    S(den2, den);
    S(den4, den2);
    M(den6, den4, den2);
    M(t, den6, num);
    M(t, t, den);

    pow2523(t, t);
    M(t, t, num);
    M(t, t, den);
    M(t, t, den);
    M(r[0], t, den);

    S(chk, r[0]);
    M(chk, chk, den);
    if (neq25519(chk, num)) M(r[0], r[0], I);

    S(chk, r[0]);
    M(chk, chk, den);
    if (neq25519(chk, num)) return -1;

    if (par25519(r[0]) === (p[31] >> 7)) Z(r[0], gf0, r[0]);

    M(r[3], r[0], r[1]);
    return 0;
}

// ----

// Converts Ed25519 public key to Curve25519 public key.
// montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p
export const convertPublicKey =  (pk:any) => {
    var z = new Uint8Array(32),
        q = [gf(false), gf(false), gf(false), gf(false)],
        a = gf(false), b = gf(false);

    if (unpackneg(q, pk)) return null; // reject invalid key

    var y = q[1];

    A(a, gf1, y);
    Z(b, gf1, y);
    inv25519(b, b);
    M(a, a, b);

    pack25519(z, a);
    return z;
}

// Converts Ed25519 secret key to Curve25519 secret key.
export const convertSecretKey =  (sk:any) => {
    let nacl = require('tweetnacl');
    var d = new Uint8Array(64), o = new Uint8Array(32), i;
    nacl.lowlevel.crypto_hash(d, sk, 32);
    d[0] &= 248;
    d[31] &= 127;
    d[31] |= 64;
    for (i = 0; i < 32; i++) o[i] = d[i];
    for (i = 0; i < 64; i++) d[i] = 0;
    return o;
}

export const convertKeyPair =  (edKeyPair:any) => {
    // console.log(edKeyPair);
    // return ;
    var publicKey = convertPublicKey(edKeyPair.publicKey);
    if (!publicKey) return null;
    return {
        publicKey: publicKey,
        secretKey: convertSecretKey(edKeyPair.secretKey)
    };
}
